# -*- coding: utf-8 -*-

from collections import deque
from StringIO import StringIO # cStringIO has issues with Unicode.

from ws9 import line, document


class LineOperation(object):
    
    def __init__(self, op_type, attribute='', value=None):
        self.operation = op_type
        self.attribute = str(attribute)
        if self.operation == 'SET':
            self.value = value
    
    def __get_operation(self):
        return self.__operation
    
    def __set_operation(self, op_type):
        if not (isinstance(op_type, basestring) and 
            op_type.upper() in 'SET DELETE NONE'.split()):
            raise ValueError('Invalid operation type: %r' % (op_type,))
        self.__operation = op_type.upper()
        if self.__operation == 'NONE':
            self.attribute = ''
    
    def __get_value(self):
        if self.operation == 'SET':
            return self.__value
        raise AttributeError(
            "'LineOperation' object has no attribute 'value'")
    
    def __set_value(self, value):
        if self.operation == 'SET':
            self.__value = value
        else:
            raise AttributeError(
                "'LineOperation' object has no attribute 'value'")
    
    def __repr__(self):
        if self.operation == 'SET':
            return 'LineOperation(%r, %r, %r)' % (self.operation,
                self.attribute, self.value)
        elif self.operation == 'NONE':
            return 'LineOperation(%r)' % (self.operation,)
        return 'LineOperation(%r, %r)' % (self.operation, self.attribute)
    
    def __call__(self, line):
        if self.operation == 'SET':
            setattr(line, self.attribute, self.value)
            return line
        elif self.operation == 'DELETE':
            delattr(line, self.attribute)
            return line
        elif self.operation == 'NONE':
            return line
    
    def __eq__(self, obj):
        if not isinstance(obj, self.__class__):
            return False
        return all(
            getattr(self, attr).__eq__(getattr(obj, attr))
            for attr in ('operation', 'attribute', 'value'))
    
    operation = property(__get_operation, __set_operation)
    value = property(__get_value, __set_value)


class StreamDocument(object):
    
    def __init__(self, raw_stream, tab_width=4):
        self.stream = get_stream(raw_stream)
        self.tab_width = int(tab_width)
        self.__indent_types = set([])
        self.__endings = set([])
        self.operations = []
        self.__iter_mode = False
    
    def __check_not_iter(self):
        if self.__iter_mode:
            raise ValueError('StreamDocument cannot be edited in iter mode.')
    
    def __check_in_iter(self):
        if not self.__iter_mode:
            raise ValueError('StreamDocument can only be read in iter mode.')
    
    def __iter__(self):
        self.__iter_mode = True
        return self
    
    def __clean_operations(self):
        self.operations = filter(
            (lambda line_op: line_op.operation != 'NONE'), self.operations)
    
    def __get_endings(self):
        if not self.__endings:
            return None
        elif len(self.__endings) == 1:
            return list(self.__endings)[0]
        return frozenset(self.__endings)
    
    def __set_endings(self, ending):
        self.__check_not_iter()
        if ending not in ['\r\n', '\r', '\n', '']:
            return
        for i, line_op in enumerate(self.operations):
            if line_op.attribute == 'ending':
                self.operations[i].operation = 'NONE'
        self.operations.append(
            LineOperation('SET', attribute='ending', value=ending))
        self.__clean_operations()
    
    def __del_endings(self):
        self.__check_not_iter()
        for i, line_op in enumerate(self.operations):
            if line_op.attribute == 'ending':
                self.operations[i].operation = 'NONE'
        self.operations.append(LineOperation('DELETE', attribute='ending'))
        self.__clean_operations()
    
    def __get_indent_type(self):
        if not self.__indent_types:
            return None
        elif len(self.__indent_types) == 1:
            return list(self.__indent_types)[0]
        return frozenset(self.__indent_types)
    
    def __set_indent_type(self, indent_type):
        self.__check_not_iter()
        if indent_type == '':
            indent_type = ' '
        indent_type = ''.join(set(indent_type))
        if indent_type not in [' ', '\t']:
            return
        for i, line_op in enumerate(self.operations):
            if line_op.attribute == 'indent_type':
                self.operations[i].operation = 'NONE'
        self.operations.append(
            LineOperation('SET', attribute='indent_type', value=indent_type))
        self.__clean_operations()
    
    def __del_indent_type(self):
        self.__check_not_iter()
        for i, line_op in enumerate(self.operations):
            if line_op.attribute == 'indent_type':
                self.operations[i].operation = 'NONE'
        self.operations.append(
            LineOperation('DELETE', attribute='indent_type'))
        self.__clean_operations()
    
    def next(self):
        self.__check_in_iter()
        next_line = line.Line(self.stream.readline(), tab_width=self.tab_width)
        if not next_line:
            raise StopIteration
        for line_op in self.operations:
            next_line = line_op(next_line)
        if next_line.ending:
            self.__endings.add(next_line.ending)
        if next_line.indent_type:
            self.__indent_types.add(''.join(next_line.indent_type))
        return next_line
    
    endings = property(__get_endings, __set_endings, __del_endings)
    indent_type = property(
        __get_indent_type, __set_indent_type,__del_indent_type)


class IteratorStream(object):
    def __init__(self, iterator, mode='r', full_buffer=False):
        self.__iterator = iterator
        self.__mode = mode
        self.__closed = False
        if full_buffer:
            self.__buffer = ''.join(line for line in iterator)
            self.__eof = True
        else:
            self.__buffer = ''
            self.__eof = False
        if 'U' in mode:
            if full_buffer:
                tmp_document = document.Document(self.__buffer)
                if isinstance(tmp_document.endings, basestring):
                    self.__newlines = set([tmp_document.endings])
                else:
                    self.__newlines = set(tmp_document.endings)
            else:
                self.__newlines = set()
    
    def __iter__(self):
        return self
    
    def __fix_newlines(self, lines):
        if 'U' in self.mode:
            split = lines.splitlines(True)
            for i, raw_line in enumerate(split):
                fixed = line.Line(raw_line)
                if fixed.ending:
                    self.__newlines.add(fixed.ending)
                fixed.ending = '\n'
                split[i] = str(fixed)
            return ''.join(split)
        return lines
    
    def __enter__(self):
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
        return False
    
    def isatty(self):
        return False
    
    def close(self):
        self.__eof = True
        self.__buffer = ''
        self.__closed = True
    
    def next(self):
        if self.closed:
            raise StopIteration
        value = self.readline()
        if value:
            return value
        raise StopIteration
    
    def read(self, bytes=-1):
        if self.closed:
            raise ValueError('I/O operation on closed stream')
        if bytes < 0:
            return ''.join(line for line in self)
        elif bytes == 0:
            return ''
        curr_line = self.readline()
        if not curr_line:
            return ''
        string = ''
        while len(string) < bytes and curr_line:
            string += curr_line
            if len(string) >= bytes:
                break
            curr_line = self.readline()
        if len(string) > bytes:
            string, prepend_buffer = string[:bytes], string[bytes:]
            self.__buffer = prepend_buffer + self.__buffer
        return string
    
    def readlines(self, bytes=-1):
        if self.closed:
            raise ValueError('I/O operation on closed stream')
        list_out = []
        curr_val = self.readline()
        while curr_val and not (bytes >= 0 ^ sum(map(len, list_out)) < bytes):
            list_out.append(curr_val)
            curr_val = self.readline()
        return list_out
    
    def readline(self, bytes=-1):
        if self.closed:
            raise ValueError('I/O operation on closed stream')
        if bytes >= 0:
            eol, rest = split_first_line(self.read(bytes))
            if rest:
                self.__buffer = rest + self.__buffer
            return self.__fix_newlines(eol)
        while True:
            if self.__eof:
                if self.__buffer:
                    value, self.__buffer = split_first_line(self.__buffer)
                    return self.__fix_newlines(value)
                return ''
            try:
                next_val = self.iterator.next()
            except StopIteration:
                self.__eof = True
            else:
                self.__buffer += next_val
                value, self.__buffer = split_first_line(self.__buffer)
                if value.endswith('\r') or value.endswith('\n'):
                    return self.__fix_newlines(value)
                self.__buffer += value
    
    def __get_closed(self):
        return self.__closed
    
    def __get_mode(self):
        return self.__mode
    
    def __get_iterator(self):
        return self.__iterator
    
    def __get_newlines(self):
        if 'U' in self.mode:
            if len(self.__newlines) == 0:
                return None
            elif len(self.__newlines) == 1:
                return list(self.__newlines)[0]
            return tuple(self.__newlines)
        raise AttributeError(
            "'IteratorStream' object has no attribute 'newlines'")
    
    closed = property(__get_closed)
    mode = property(__get_mode)
    iterator = property(__get_iterator)
    newlines = property(__get_newlines)


def get_stream(raw_stream):
    if isinstance(raw_stream, basestring):
        return IteratorStream(iter(raw_stream.splitlines(True)))
    elif hasattr(raw_stream, '__iter__'):
        return IteratorStream(iter(raw_stream))
    else:
        return IteratorStream(line_reader(raw_stream))


def line_reader(fp, chunks=65536):
    data = ''
    while True:
        old_data = data[:]
        data += fp.read(chunks)
        if data == '':
            break
        if ('\r' in data) or ('\n' in data):
            curr_line, data = split_first_line(data)
            yield curr_line
        if old_data == data:
            yield data
            break


def split_first_line(string):
    split = string.splitlines(True) or ['']
    return split[0], ''.join(split[1:])
